#ifndef SIMULATION_H
#define SIMULATION_H

#include <QString>

#include "ngl/Camera.h"
#include "ngl/Colour.h"
#include "ngl/SpotLight.h"
#include "ngl/TransformStack.h"

#include "ShaderLibrary.h"
#include "Solver.h"
#include "Environment.h"
#include "Cache.h"

#include <QtOpenGL>

/// @file Simulation.h
/// @brief manages the simulation and communication to and from the gui
/// @author Perseedoss Rajiv
/// @version 1.0
/// @date 02.06.11
/// Revision History :
/// Initial Version 02.06.11
/// @class Simulation
/// @brief creates all other objects for the simulation and provides central management and communication with all of them and the gui

class Simulation : public QGLWidget
{
Q_OBJECT

public:
    /// @brief ctor
    /// @param [in] _parent the parent window to create the GL context in
    Simulation(
            QWidget* _parent
        );

    /// @brief dtor
    ~Simulation();

    /// @brief handle keypress events
    /// @param[in] _event the Qt Event structure, holding information about keypresses
    void keyPress(QKeyEvent* _event);

    /// @brief returns the solver of the simulation
    inline Solver* getSolver() const { return  m_solver; }

    /// @brief returns the environment object
    inline Environment* getEnvironment() const { return  m_environment; }

    /// @brief returns the integration object
    inline Integration* getIntegration() const { return  m_integration; }

    /// @brief this method is used to setup the 2D top view
    void setup2DTopView();

    /// @brief this method is used to setup the 2D front view
    void setup2DFrontView();

    /// @brief this method toggles the cache timer
    void toggleCache();

    /// @brief this method toggles the automatic cache flush timer
    void toggleAutomaticCacheFlush();

    /// @brief this method updates the states of timers after the simulation of fluid is started/stopped
    void updateFPSTimer();

    /// @brief this method flushes the cache to disk
    void flushCache();


    /// @brief return the whether cache is enabled
    inline bool getCacheEnabled() const { return m_cacheEnabled; }

    /// @brief return cache samping interval
    inline int getCacheSamplingInterval() const { return m_cacheSamplingInterval; }

    /// @brief return whether automatic cache flushing is enabled
    inline bool getCacheAutomaticFlushEnabled () const { return m_cacheAutomaticFlushEnabled; }

    /// @brief return automatic cache flushing interval
    inline int getCacheAutomaticFlushInterval() const { return m_cacheAutomaticFlushInterval; }

    /// @brief return whether rbd is cached
    inline bool getCacheExportRBDEnabled() const { return m_cacheExportRBDEnabled; }

    /// @brief return boundary is cached
    inline bool getCacheExportBoundaryEnabled() const { return m_cacheExportBoundaryEnabled; }

    /// @brief return the path of the cache on disk
    inline std::string getCachePath() const { return m_cachePath; }


    /// @brief set flag to determine whether cache is enabled
    /// @param[in] _v updated flag
    inline void setCacheEnabled(const bool _v) { m_cacheEnabled = _v; }

    /// @brief return cache samping interval
    /// @param[in] _v updated value
    inline void setCacheSamplingInterval(const int _v) { m_cacheSamplingInterval = _v; }

    /// @brief set flag to determine whether automatic cache flushing is enabled
    /// @param[in] _v updated flag
    inline void setCacheAutomaticFlushEnabled (const bool _v) { m_cacheAutomaticFlushEnabled = _v; }

    /// @brief return automatic cache flushing interval
    /// @param[in] _v updated value
    inline void setCacheAutomaticFlushInterval(const int _v) { m_cacheAutomaticFlushInterval = _v; }

    /// @brief set flag to determine whether rbd is cached
    /// @param[in] _v updated flag
    inline void setCacheExportRBDEnabled(const bool _v) { m_cacheExportRBDEnabled = _v; }

    /// @brief set flag to determine whether boundary is cached
    /// @param[in] _v updated flag
    inline void setCacheExportBoundaryEnabled(const bool _v) { m_cacheExportBoundaryEnabled = _v; }


public slots:
    /// @brief called when the camera zoom slider value is changed in the GUI; it slides the camera along its N vector
    /// @param[in] _depth the zoom factor to set to the camera
    void updateCameraZoom(const int _depth);


protected:
    /// @brief called when the window context is created
    void initializeGL();

    /// @brief called whenever the window is re-sized
    /// @param[in] _w the width of the resized window
    /// @param[in] _h the height of the resized window
    void resizeGL
                (
                    const int _w,
                    const int _h
                );

    /// @brief main gl drawing method that is called whenever the window needs to be re-drawn
    void paintGL();


private :
    /// @brief the x rotation mouse value
    int m_spinXFace;

    /// @brief the y rotation mouse value
    int m_spinYFace;

    /// @brief flag to indicate if the mouse button is pressed when dragging
    bool m_rotate;

    /// @brief the previous x mouse value
    int m_origX;

    /// @brief the previous y mouse value
    int m_origY;

    /// @brief right mouse camera movement x value
    int m_moveX;

    /// @brief right mouse camera movement y value
    int m_moveY;

    /// @brief flag to indicate if the right mouse is pressed for (x,y) mouse movement
    bool m_move;

    /// @brief starting x position of the right mouse
    int m_origMoveX;

    /// @brief starting y position of the right mouse
    int m_origMoveY;

    /// @brief toggle help flag
    bool m_drawHelp;

    /// @brief aspect ratio of the camera
    float m_aspect;

    /// @brief mouse move sensitivity
    int m_mouseMoveSensitivity;

    /// @brief frame update rate
    int m_frameRate;

    /// @brief global transform stack
    ngl::TransformStack m_transformStack;

    /// @brief ngl light object
    ngl::Light m_light;

    /// @brief ngl camera object
    ngl::Camera m_camera;

    /// @brief shader library
    ShaderLibrary* m_shaderLibrary;

    /// @brief fluid solver
    Solver* m_solver;

    /// @brief environment
    Environment* m_environment;

    /// @brief integration
    Integration* m_integration;

    /// @brief cache
    Cache* m_cache;

    /// @brief the last depth of a camera zoom operation
    float m_lastCamZ;

    /// @brief counter to keep track of number of frames for fps calculation
    int m_frameCounter;

    /// @brief main simulation timer (continuous ticking)
    int m_mainSimulationTimer;

    /// @brief fps timer with interval = 1 s
    QTimer* m_fpsTimer;

    /// @brief fps timer toggle
    bool m_fpsTimerEnabled;

    /// @brief simulation cache timer
    QTimer* m_simulationCacheTimer;

    /// @brief simulation cache autoFlush timer
    QTimer* m_simulationCacheAutoFlushTimer;

    /// @brief cache frame number
    int m_simulationCacheFrameNumber;

    /// @brief flag to determine if cache is enabled
    bool m_cacheEnabled;

    /// @brief cache samping interval
    int m_cacheSamplingInterval;

    /// @brief flag to determine automatic cache flushing is enabled
    bool m_cacheAutomaticFlushEnabled;

    /// @brief automatic cache flushing interval
    int m_cacheAutomaticFlushInterval;

    /// @brief flag to determine if rbd is cached
    bool m_cacheExportRBDEnabled;

    /// @brief flag to determine if boundary is cached
    bool m_cacheExportBoundaryEnabled;

    /// @brief the path of the cache on disk
    std::string m_cachePath;


private :
    /// @brief the timer event triggered from the timers
    void timerEvent(QTimerEvent* _event);

    /// @brief this method create our camera
    void createCamera();

    /// @brief this method create our light
    void createLight();

    /// @brief this method display the text help overlay on the screen
    void displayHelp();

    /// @brief this method is called every time a mouse is moved
    /// @param[in] _event the Qt Event structure
    void mouseMoveEvent(
                            QMouseEvent* _event
                        );

    /// @brief this method is called everytime the mouse button is pressed
    /// @param _event the Qt Event structure
    void mousePressEvent(
                            QMouseEvent* _event
                        );

    /// @brief this method is called everytime the mouse button is released
    /// @param _event the Qt Event structure
    void mouseReleaseEvent(
                            QMouseEvent* _event
                            );

    /// @brief this method is called to initialise all the objects/params of the simulation
    void initialiseSimulation();

    /// @brief this method is called to delete all the objects of the simulation
    void deleteSimulationObjects();



private slots:
    /// @brief this method is called at every fps timer tick
    void processFPS();

    /// @brief this method is called at every simulation timer tick
    void processSimulationCache();

    /// @brief this method is called to automatically flush the cache
    void processSimulationCacheAutoFlush();


};
#endif // SIMULATION_H
